<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<!-- employ 'Annotations' module -->

<link rel="import" href="../lib/annotations/annotations.html">
<link rel="import" href="../lib/resolve-url.html">
<link rel="import" href="../lib/path.html">
<script>

/**
 * Scans a template to produce an annotation object that stores expression
 * metadata along with information to associate the metadata with nodes in an
 * instance.
 *
 * Elements with `id` in the template are noted and marshaled into an
 * the `$` hash in an instance.
 *
 * Example
 *
 *     &lt;template>
 *       &lt;div id="foo">&lt;/div>
 *     &lt;/template>
 *     &lt;script>
 *      Polymer({
 *        task: function() {
 *          this.$.foo.style.color = 'red';
 *        }
 *      });
 *     &lt;/script>
 *
 * Other expressions that are noted include:
 *
 * Double-mustache annotations in text content. The annotation must be the only
 * content in the tag, compound expressions are not (currently) supported.
 *
 *     <[tag]>{{path.to.host.property}}<[tag]>
 *
 * Double-mustache annotations in an attribute.
 *
 *     <[tag] someAttribute="{{path.to.host.property}}"><[tag]>
 *
 * Only immediate host properties can automatically trigger side-effects.
 * Setting `host.path` in the example above triggers the binding, setting
 * `host.path.to.host.property` does not.
 *
 * `on-` style event declarations.
 *
 *     <[tag] on-<event-name>="{{hostMethodName}}"><[tag]>
 *
 * Note: **the `annotations` feature does not actually implement the behaviors
 * associated with these expressions, it only captures the data**.
 *
 * Other optional features contain actual data implementations.
 *
 * @class standard feature: annotations
 */

/*

Scans a template to produce an annotation map that stores expression metadata
and information that associates the metadata to nodes in a template instance.

Supported annotations are:

  * id attributes
  * binding annotations in text nodes
    * double-mustache expressions: {{expression}}
    * double-bracket expressions: [[expression]]
  * binding annotations in attributes
    * attribute-bind expressions: name="{{expression}} || [[expression]]"
    * property-bind expressions: name*="{{expression}} || [[expression]]"
    * property-bind expressions: name:="expression"
  * event annotations
    * event delegation directives: on-<eventName>="expression"

Generated data-structure:

  [
    {
      id: '<id>',
      events: [
        {
          mode: ['auto'|''],
          name: '<name>'
          value: '<expression>'
        }, ...
      ],
      bindings: [
        {
          kind: ['text'|'attribute'|'property'],
          mode: ['auto'|''],
          name: '<name>'
          value: '<expression>'
        }, ...
      ],
      // TODO(sjmiles): confusingly, this is annotation-parent, not node-parent
      parent: <reference to parent annotation>,
      index: <integer index in parent's childNodes collection>
    },
    ...
  ]

TODO(sjmiles): this module should produce either syntactic metadata
(e.g. double-mustache, double-bracket, star-attr), or semantic metadata
(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half.

*/

  Polymer.Base._addFeature({

    // registration-time

    _prepAnnotations: function() {
      if (!this._template) {
        this._notes = [];
      } else {
        // TODO(sorvell): ad hoc method of plugging behavior into Annotations
        var self = this;
        Polymer.Annotations.prepElement = function(element) {
          self._prepElement(element);
        }
        if (this._template._content && this._template._content._notes) {
          this._notes = this._template._content._notes;
        }  else {
          this._notes = Polymer.Annotations.parseAnnotations(this._template);
          this._processAnnotations(this._notes);
        }
        Polymer.Annotations.prepElement = null;
      }
    },

    _processAnnotations: function(notes) {
      for (var i=0; i<notes.length; i++) {
        var note = notes[i];
        // Parse bindings for methods & path roots (models)
        for (var j=0; j<note.bindings.length; j++) {
          var b = note.bindings[j];
          for (var k=0; k<b.parts.length; k++) {
            var p = b.parts[k];
            if (!p.literal) {
              var signature = this._parseMethod(p.value);
              if (signature) {
                p.signature = signature;
              } else {
                p.model = Polymer.Path.root(p.value);
              }
            }
          }
        }
        // Recurse into nested templates & bind parent props
        if (note.templateContent) {
          this._processAnnotations(note.templateContent._notes);
          var pp = note.templateContent._parentProps =
            this._discoverTemplateParentProps(note.templateContent._notes);
          var bindings = [];
          for (var prop in pp) {
            var name = '_parent_' + prop;
            bindings.push({
              index: note.index,
              kind: 'property',
              name: name,
              propertyName: name,
              parts: [{
                mode: '{',
                model: prop,
                value: prop
              }]
            });
          }
          note.bindings = note.bindings.concat(bindings);
        }
      }
    },

    // Finds all bindings in template content and stores the path roots in
    // the path members in content._parentProps. Each outer template merges
    // inner _parentProps to propagate inner parent property needs to outer
    // templates.
    _discoverTemplateParentProps: function(notes) {
      var pp = {};
      for (var i=0, n; (i<notes.length) && (n=notes[i]); i++) {
        // Find all bindings to parent.* and spread them into _parentPropChain
        for (var j=0, b$=n.bindings, b; (j<b$.length) && (b=b$[j]); j++) {
          for (var k=0, p$=b.parts, p; (k<p$.length) && (p=p$[k]); k++) {
            if (p.signature) {
              var args = p.signature.args;
              for (var kk=0; kk<args.length; kk++) {
                var model = args[kk].model;
                if (model) {
                  pp[model] = true;
                }
              }
              if (p.signature.dynamicFn) {
                pp[p.signature.method] = true;
              }
            } else {
              if (p.model) {
                pp[p.model] = true;
              }
            }
          }
        }
        // Merge child _parentProps into this _parentProps
        if (n.templateContent) {
          var tpp = n.templateContent._parentProps;
          Polymer.Base.mixin(pp, tpp);
        }
      }
      return pp;
    },

    _prepElement: function(element) {
      Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument);
    },

    // instance-time

    _findAnnotatedNode: Polymer.Annotations.findAnnotatedNode,

    // marshal all teh things
    _marshalAnnotationReferences: function() {
      if (this._template) {
        this._marshalIdNodes();
        this._marshalAnnotatedNodes();
        this._marshalAnnotatedListeners();
      }
    },

    // push configuration references at configure time
    _configureAnnotationReferences: function() {
      var notes = this._notes;
      var nodes = this._nodes;
      for (var i=0; i<notes.length; i++) {
        var note = notes[i];
        var node = nodes[i];
        this._configureTemplateContent(note, node);
        this._configureCompoundBindings(note, node);
      }
    },

    // nested template contents have been stored prototypically to avoid
    // unnecessary duplication, here we put references to the
    // indirected contents onto the nested template instances
    _configureTemplateContent: function(note, node) {
      if (note.templateContent) {
        // note: we can rely on _nodes being set here and having the same
        // index as _notes
        node._content = note.templateContent;
      }
    },

    // Compound bindings utilize private storage on the node to store
    // the current state of each value that will be concatenated to generate
    // the final property/attribute/text value
    // Here we initialize the private storage array on the node with any
    // literal parts that won't change (could get fancy and use WeakMap),
    // and configure property bindings to children with the literal parts
    // (textContent and annotations were already initialized in the template)
    _configureCompoundBindings: function(note, node) {
      var bindings = note.bindings;
      for (var i=0; i<bindings.length; i++) {
        var binding = bindings[i];
        if (binding.isCompound) {
          // Create compound storage map
          var storage = node.__compoundStorage__ ||
            (node.__compoundStorage__ = {});
          var parts = binding.parts;
          // Copy literals from parts into storage for this binding
          var literals = new Array(parts.length);
          for (var j=0; j<parts.length; j++) {
            literals[j] = parts[j].literal;
          }
          var name = binding.name;
          storage[name] = literals;
          // Configure properties with their literal parts
          if (binding.literal && binding.kind == 'property') {
            if (node._configValue) {
              node._configValue(name, binding.literal);
            } else {
              node[name] = binding.literal;
            }
          }
        }
      }
    },

    // construct `$` map (from id annotations)
    _marshalIdNodes: function() {
      this.$ = {};
      for (var i=0, l=this._notes.length, a; (i<l) && (a=this._notes[i]); i++) {
        if (a.id) {
          this.$[a.id] = this._findAnnotatedNode(this.root, a);
        }
      }
    },

    // concretize `_nodes` map (from anonymous annotations)
    _marshalAnnotatedNodes: function() {
      if (this._notes && this._notes.length) {
        var r = new Array(this._notes.length);
        for (var i=0; i < this._notes.length; i++) {
          r[i] = this._findAnnotatedNode(this.root, this._notes[i]);
        }
        this._nodes = r;
      }
    },

    // install event listeners (from event annotations)
    _marshalAnnotatedListeners: function() {
      for (var i=0, l=this._notes.length, a; (i<l) && (a=this._notes[i]); i++) {
        if (a.events && a.events.length) {
          var node = this._findAnnotatedNode(this.root, a);
          for (var j=0, e$=a.events, e; (j<e$.length) && (e=e$[j]); j++) {
            this.listen(node, e.name, e.value);
          }
        }
      }
    }

  });

</script>
